/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/
#ifndef GIMI_CONNECTOR_H
#define GIMI_CONNECTOR_H

#include <signal.h>
#include <inttypes.h>
#include <list>

#include "gimiutils.h"
#include "sync.hpp"
#include "thread.hpp"
#include "GIMnetAP/protocolDispatcher.h"
#include "GIMnetAP/fastProtocolHdr.h"
#include "GIMnetAP/hubctrlHdr.h"
#include "GIMnetAP/msgreplybuffer.h"
#include "apnameserviceframe.h"
#include "namecache.hpp"

namespace gimi {
  
#define SOCKETTIMEOUT   500

  enum EMulticastType {
    KMulticastTypeServiceDiscovery = 1
  };

  enum EConnectorLock {
    KConnectorLockReceive = 0,
    KConnectorLockSend    = 1
  };

  // Forward declaration
  class GIMICore;
  /**
   * Main connection-handler class. Has a thread that reads data from
   * socket-interface and forwards received data for handling. That thread also
   * handles reconnecting if necessary. 
   * 
   * Acts also as a wrapper for sending data using TcpHubProtocolHandler-class.
   * Sending logic also handles local loopback and forwards messages to own client
   * directly to handlers instead of sending them to socket-interface.
   */
  class Connector : private gim::CSync, private gim::CThread
  {
  public:
    /**
     * 
     */
    Connector(GIMICore* aGimiPtr);
    
    /**
     * Connections are closed and cleaned up.
     */
    ~Connector();
    
    int connectToHub(const std::string &hubAddress,
                     const int hubPort,
                     const std::string &hubOwnName,
                     const bool aForceNameRegistration,
                     const uint8_t *aHubCtrlSecretClientKey,
                     const unsigned int aHubCtrlSecretClientKeySize,
                     const int aTimeout_ms);

    int sendDataToTarget(const uint32_t targetId,
                         const uint8_t* data,
                         const unsigned int datalength);
    
    int sendDataMulticast(const t_clientIdList &targetIds,
                          const uint8_t* data,
                          const unsigned int datalength);

    
    int findClientId(uint32_t &clientId,
                     const std::string &clientName,
                     const unsigned int &aTimeout_ms = 60000);
   
    int findClientName(std::string& clientName,
                       const uint32_t &clientId,
                       const unsigned int &aTimeout_ms);


    inline int GetClientNameById(std::string& aClientName,
                                 const uint32_t &aClientNodeId,
                                 const unsigned int &aTimeout_ms) {
      return findClientName(aClientName, aClientNodeId, aTimeout_ms);
    }

    /** Sends a ServiceDiscovery type multicast.
     *
     * This function is wrapper for GIMnetAP Multicast service. Message sent
     * with this method is sent to nearest currently known Multicast Service.
     * \todo TBD!
     */
    bool sendDataByServerMulticast(unsigned int &aDeliveredCount,
                                   const uint8_t *aData, const unsigned int &aLength,
                                   const EMulticastType &aType,
                                   const unsigned int aTimeout_ms);

    uint32_t sendNameServiceQuery(const EAPNameServiceRequest &aRequest,
                                  const std::string &aName,
                                  const uint32_t &aNodeId,
                                  const uint32_t &aAge_Left);
    
    inline int getHubId() const { return iOwnHubId; }
    inline const std::string& getHubName() const { return iOwnHubName; }

    inline int getHubPort() const { return iHubPort; }
    inline const std::string& getHubAddress() const { return iHubAddress; }

    inline bool isConnected() const { return iIsActive; }
    inline bool isActive() const { return iIsConnected; }
    
    inline void stop() { CloseConnection(); }
    void CloseConnection(void);


    inline void SetMulticastNodeId(const uint32_t &aNodeId) { 
      iMulticastNodeId = aNodeId;
    }
    inline void SetMulticastServiceDiscoveryGroupId(const uint32_t &aGroupId) { 
      iMulticastServiceDiscoveryGroupId = aGroupId;
    }
    inline void SetMulticastServiceDiscoveryAccessKey(const uint64_t &aAccessKey) { 
      iMulticastServiceDiscoveryAccessKey = aAccessKey;
    }
    

    void NameRegistrationRefresh(const unsigned int &aTimeout_ms);


    /// Get Congestion level for the given node. 
    int GetCongestionLevel(const uint32_t &aNodeId);

    /// Get existing error for given node. false if no errors, true if errors.
    bool GetError(const uint32_t &aNodeId);

    
    




    ActiveStatus reconnectedStatus; ///< Succesful reconnect sets to true,
                                    ///< used for upkeep-operations in GIMICore (UpkeepThread)
                                    ///< after reconnect. GIMICore will set this back to false
                                    ///< after doing upkeep.
    
  private:

    uint32_t iOwnHubId;
    std::string iOwnHubName;
    std::string iHubAddress;
    int iHubPort;
    bool iForceNameRegistration;
    
  private:
    volatile bool iIsActive;
    volatile bool iIsConnected;

    // Connector Core features
    TProtocolDisp iProtocolDisp;                  ///< Core communication interface
    GIMICore *iGIMICorePtr;                       ///< Upper level provided pointer to GIMI-Core


    // HubCtrl (GIMnetAP Core feature) parameters
    uint8_t *iHubCtrlSecretKey;                   ///< Pointer to data in which HubCtrl secret key is stored
    unsigned int iHubCtrlSecretKeySize;           ///< Size of HubCtrl secret key
    TMsgReplyBuffer iHubCtrlMsgReplyBuffer;       ///< ReplyBuffer for storing HubCtrl messages
    uint32_t iHubCtrlMessageSeq;                  ///< Current message Sequence
    unsigned int iHubCtrlMessageValidySeconds;        ///< HubCtrl auth message validity in seconds


    // Name service parameters
    uint32_t iNameServiceNodeId;                  ///< Name Service NodeID
    uint32_t iNameServiceMessageSeq;              ///< Current MessageSeq for Nameservice
    uint32_t iNameServiceOwnNameValidity;         ///< Own name requested Validity time (seconds)
    unsigned int iNameServiceRefreshIntervalMs;   ///< NameService name refresh interval in ms.
    gim::time iNameServiceNextRefreshTime;        ///< Time of next refresh.
    TMsgReplyBuffer iNameServiceMsgReplyBuffer;   ///< NameService messageReplyBuffer
    CNameCache iNameCache;


    // Multicast Services ) required parameters.
    uint32_t iMulticastNodeId;                    ///< Multicast NodeId
    uint32_t iMulticastMessageSeq;                ///< Current MessageSeq for Multicast
    TMsgReplyBuffer iMulticastMsgReplyBuffer;     ///< ReplyMessageBuffer for Multicast service
    // Multicast/ServiceDiscovery related parameters.
    uint32_t iMulticastServiceDiscoveryGroupId;   ///< Multicast/ServiceDiscovery GroupId
    uint64_t iMulticastServiceDiscoveryAccessKey; ///< Multicast/ServiceDiscovery AccessKey


    std::map<uint32_t, unsigned int> iCongestionLevelArray; 
    std::map<uint32_t, bool> iErrorArray;

  private:
    bool ExecuteHubCtrlRegisterAPClient(const unsigned int &aTimeout_ms);
    bool ExecuteHubCtrlQueryLocalNodeId(const unsigned int &aTimeout_ms);

    bool ExecuteNameServiceRegisterName(const unsigned int &aTimeout_ms);
    bool ExecuteNameServiceRegisterNameRefresh(const unsigned int &aTimeout_ms);
    bool ExecuteNameServiceUnregisterName(const unsigned int &aTimeout_ms);

    bool ExecuteMulticastServiceSubscribe(const unsigned int &aTimeout_ms);
    bool ExecuteMulticastServiceUnsubscribe(const unsigned int &aTimeout_ms);
    
    bool ExecuteMulticastServiceJoinAnnouncement(const unsigned int &aTimeout_ms);

  private:
    void processNameServiceMessage(const TFastProtocolHeader *frame,
                                   const uint8_t * &payload,
                                   const unsigned int &payloadsize);
    void processMulticastServiceMessage(const TFastProtocolHeader *frame,
                                        const uint8_t * &payload,
                                        const unsigned int &payloadsize);
    int processFastProtocolGIMIPayload(const TFastProtocolHeader *frame,
                                       const uint8_t * &payload,
                                       const unsigned int &payloadsize);
    int ExecuteClientRegistration(const unsigned int &aTimeout_ms);
    void ExecuteThreads(void);
    void TerminateThreads(void);
    void hubctrlProtocolCallback(const uint8_t *data,
                                 const int size);
    void fastProtocolCallback(const uint8_t *data,
                              const int size);
    void processQueryLocalNodeIdReply(const THubCtrlProtocolHeader *frame,
                                      const unsigned int size,
                                      TProtocolDisp *link);
    void processRegisterAPClientReply(const THubCtrlProtocolHeader *frame,
                                      const unsigned int size,
                                      TProtocolDisp *link);
    uint32_t GetMulticastGroupIdByMulticastType(const EMulticastType &aMulticastType);  

  private:
    int ThreadFunction(const int aThreadNumber);
    int DataReceiverThreadFunc();
  private:    
    static void hubctrlProtocolCallbackFunc(int protocol_number, const unsigned char *data,
                                            int size, void *userPtr);
    static void fastProtocolCallbackFunc(int protocol_number, const unsigned char *data,
                                         int size, void *userPtr);

  private:
    // Prevent use.
    Connector(const Connector& c);
    const Connector& operator=(const Connector& right);

  };
  
}

#endif // GIMI_CONNECTOR_H
